/*
FxSound
Copyright (C) 2023  FxSound LLC

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "codedefs.h"

/* Standard includes */
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <stdio.h>
#include <math.h>

#include <mmreg.h>
#include <Mmdeviceapi.h>
#include <Audioclient.h>
#include <Functiondiscoverykeys_devpkey.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>

#include "slout.h"
#include "mry.h"
#include "u_sndDevices.h"
#include "sndDevices.h"

// These are for registering the volume callbacks. For some reason the callbacks would crash
// when these were objects in the handle, perhaps because it was allocated?
//CsndDevicesAudioEndpointVolumeCallbackCapture  g_EPVolEventsCapture;
//CsndDevicesAudioEndpointVolumeCallbackPlayback g_EPVolEventsPlayback;

//-----------------------------------------------------------
// Client implementation of IAudioEndpointVolumeCallback
// interface. When a method in the IAudioEndpointVolume
// interface changes the volume level or muting state of the
// endpoint device, the change initiates a call to the
// client's IAudioEndpointVolumeCallback::OnNotify method.
//-----------------------------------------------------------
// PTNOTE - since we have 2 volume controls being changed we need unique callback function
// for capture and playback to be able to distinguish which control generated the callback.

// Implementation of the Capture callback functions.
CsndDevicesAudioEndpointVolumeCallbackCapture::CsndDevicesAudioEndpointVolumeCallbackCapture() :  _cRef(1)
{
}

CsndDevicesAudioEndpointVolumeCallbackCapture::~CsndDevicesAudioEndpointVolumeCallbackCapture()
{
}

// IUnknown methods -- AddRef, Release, and QueryInterface

ULONG STDMETHODCALLTYPE CsndDevicesAudioEndpointVolumeCallbackCapture::AddRef()
{
  return InterlockedIncrement(&_cRef);
}

ULONG STDMETHODCALLTYPE CsndDevicesAudioEndpointVolumeCallbackCapture::Release()
{
  ULONG ulRef = InterlockedDecrement(&_cRef);
  if (0 == ulRef)
  {
		delete this;
  }
  return ulRef;

}

HRESULT STDMETHODCALLTYPE CsndDevicesAudioEndpointVolumeCallbackCapture::QueryInterface(REFIID riid, VOID **ppvInterface)
{
  if (IID_IUnknown == riid)
  {
		AddRef();
		*ppvInterface = (IUnknown*)this;
  }
  else if (__uuidof(IAudioEndpointVolumeCallback) == riid)
  {
		AddRef();
		*ppvInterface = (IAudioEndpointVolumeCallback*)this;
  }
  else
  {
		*ppvInterface = NULL;
		return E_NOINTERFACE;
  }
  return S_OK;
}

// PTNOTE - this is the callback hit when the capture volume controls is moved.
HRESULT STDMETHODCALLTYPE CsndDevicesAudioEndpointVolumeCallbackCapture::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify)
{
	HRESULT hr;
	float captureVolSetting;
   struct sndDevicesHdlType *cast_handle;
	BOOL captureMute;
    
   cast_handle = (struct sndDevicesHdlType *)g_sndDevicesVolumeCallbackCapture_hdl;

   if (cast_handle == NULL)
     return(S_FALSE);

	if (pNotify == NULL)
	{
		return E_INVALIDARG;
	}

	// Ignore any callbacks caused by our own volume setting calls in this app.
	if( pNotify->guidEventContext != cast_handle->guidThisApplication )
	{
		// Recovers capture volume control setting in a nomalized range of 0.0 to 1.0
		hr = cast_handle->pEndptVolCapture->GetMasterVolumeLevelScalar(&captureVolSetting);

		// Note, second arg is a GUID used in callbacks generated by this change to identify who made the change.
		if( cast_handle->pEndptVolPlayback != NULL)
			hr = cast_handle->pEndptVolPlayback->SetMasterVolumeLevelScalar(captureVolSetting, &(cast_handle->guidThisApplication) );

		// Get mute setting from capture device.
		hr = cast_handle->pEndptVolCapture->GetMute(&captureMute);

		// Set mute on playback device to match capture device
		hr = cast_handle->pEndptVolPlayback->SetMute(captureMute, &(cast_handle->guidThisApplication) );
		if( (hr != S_OK) && (hr != S_FALSE) )	// Note, will return S_FALSE if the mute was already off, check for other errors.
		{
			cast_handle->function_status = SND_DEVICES_SET_MUTE_FAILED;
			return(hr);
		}
	}
	return S_OK;
}

// Function to be called from sndDevicesInit to pass in allocated handle for by methods in this class.
void CsndDevicesAudioEndpointVolumeCallbackCapture::SetPtHandle(PT_HANDLE *sndDevices_hdl)
{
	g_sndDevicesVolumeCallbackCapture_hdl = sndDevices_hdl;
}

// Implementation of the Playback callback functions
CsndDevicesAudioEndpointVolumeCallbackPlayback::CsndDevicesAudioEndpointVolumeCallbackPlayback() :  _cRef(1)
{
}

CsndDevicesAudioEndpointVolumeCallbackPlayback::~CsndDevicesAudioEndpointVolumeCallbackPlayback()
{
}

// IUnknown methods -- AddRef, Release, and QueryInterface

ULONG STDMETHODCALLTYPE CsndDevicesAudioEndpointVolumeCallbackPlayback::AddRef()
{
  return InterlockedIncrement(&_cRef);
}

ULONG STDMETHODCALLTYPE CsndDevicesAudioEndpointVolumeCallbackPlayback::Release()
{
  ULONG ulRef = InterlockedDecrement(&_cRef);
  if (0 == ulRef)
  {
		delete this;
  }
  return ulRef;

}

HRESULT STDMETHODCALLTYPE CsndDevicesAudioEndpointVolumeCallbackPlayback::QueryInterface(REFIID riid, VOID **ppvInterface)
{
  if (IID_IUnknown == riid)
  {
		AddRef();
		*ppvInterface = (IUnknown*)this;
  }
  else if (__uuidof(IAudioEndpointVolumeCallback) == riid)
  {
		AddRef();
		*ppvInterface = (IAudioEndpointVolumeCallback*)this;
  }
  else
  {
		*ppvInterface = NULL;
		return E_NOINTERFACE;
  }
  return S_OK;
}

// PTNOTE - this is the callback hit when the playback volume control is moved.
HRESULT STDMETHODCALLTYPE CsndDevicesAudioEndpointVolumeCallbackPlayback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify)
{
	HRESULT hr;
	float playbackVolSetting;
   struct sndDevicesHdlType *cast_handle;
	BOOL playbackMute;
    
   cast_handle = (struct sndDevicesHdlType *)g_sndDevicesVolumeCallbackPlayback_hdl;

   if (cast_handle == NULL)
     return(S_FALSE);

	if (pNotify == NULL)
	{
		return E_INVALIDARG;
	}

	// Ignore any callbacks caused by our own volume setting calls in this app.
	if( pNotify->guidEventContext != cast_handle->guidThisApplication )
	{
		// Recovers playback volume control setting in a nomalized range of 0.0 to 1.0
		hr = cast_handle->pEndptVolPlayback->GetMasterVolumeLevelScalar(&playbackVolSetting);

		// Note, second arg is a GUID used in callbacks generated by this change to identify who made the change.
		if( cast_handle->pEndptVolCapture != NULL)
			hr = cast_handle->pEndptVolCapture->SetMasterVolumeLevelScalar(playbackVolSetting, &(cast_handle->guidThisApplication) );

		// Get mute setting from playback device.
		hr = cast_handle->pEndptVolPlayback->GetMute(&playbackMute);

		// Set mute on capture device to match playback device
		hr = cast_handle->pEndptVolCapture->SetMute(playbackMute, &(cast_handle->guidThisApplication) );
		if( (hr != S_OK) && (hr != S_FALSE) )	// Note, will return S_FALSE if the mute was already off, check for other errors.
		{
			cast_handle->function_status = SND_DEVICES_SET_MUTE_FAILED;
			return(hr);
		}
	}
	return S_OK;
}

// Function to be called from sndDevicesInit to pass in allocated handle for by methods in this class.
void CsndDevicesAudioEndpointVolumeCallbackPlayback::SetPtHandle(PT_HANDLE *sndDevices_hdl)
{
	g_sndDevicesVolumeCallbackPlayback_hdl = sndDevices_hdl;
}